package com.credithc.eagleeye.plugins.feign;
import com.credithc.eagleeye.agent.factory.EagleEyeAgentFactory;
import com.credithc.eagleeye.agent.util.HostUtil;
import com.credithc.eagleeye.constants.RpcTypeEnum;
import com.credithc.eagleeye.exception.EagleEyeIgnoreException;
import com.credithc.eagleeye.plugins.util.LogUtil;
import com.credithc.eagleeye.thread.EagleEyeThreadContext;
import com.credithc.eagleeye.thread.TraceInfo;
import com.credithc.eagleeye.util.MixUtil;
import com.credithc.eagleeye.util.ThrowableToStringArray;
import com.credithc.eagleeye.vo.RpcTraceInfoVo;
import feign.Client;
import feign.Request;
import feign.Response;
import feign.Util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import static java.nio.charset.StandardCharsets.UTF_8;
/**
 * 描 述: <br/>
 * 作 者: zhangjin<br/>
 * 创 建：2019年11月04日<br/>
 * 版 本：v1.0.0<br>
 * <p>
 * 历 史: (版本) 作者 时间 注释 <br/>
 */
@SuppressWarnings("DuplicatedCode")
@Slf4j
public class EagleEyeFeignClient implements Client {
    private final Client delegate;

    public EagleEyeFeignClient(Client delegate) {
        this.delegate = delegate;
    }

    @Override
    public Response execute(Request request, Request.Options options) throws IOException {
        log.debug("EagleEyeFeignClient around start...");
        Response response = null;
        Throwable error = null;
        long beginTime = System.currentTimeMillis();
        RpcTraceInfoVo rpcTraceInfoVo =new RpcTraceInfoVo();
        try {
            TraceInfo traceInfo = EagleEyeThreadContext.getTraceInfo();
            if (traceInfo == null) {
                traceInfo = new TraceInfo();
                EagleEyeThreadContext.putTraceInfo(traceInfo);
                MDC.put("traceId", traceInfo.getTraceId());
                MDC.put("rpcId", traceInfo.getRpcId());
            } else {
                traceInfo.addSequenceNo();
            }

            initRpcTraceInfoVo(rpcTraceInfoVo,request, traceInfo);
            response = delegate.execute(modifiedRequest(request, traceInfo), options);
            rpcTraceInfoVo.setRunTime(System.currentTimeMillis() - beginTime);
            response=fillResponseRpcTraceInfoVo(rpcTraceInfoVo,response);
        } catch(EagleEyeIgnoreException ex){
            rpcTraceInfoVo.setResult("OK");
        }catch(Exception ex){
            //ERROR
            rpcTraceInfoVo.setRunTime(System.currentTimeMillis()-beginTime);
            rpcTraceInfoVo.setResult("ERROR");
            //异常的结果
            String traceStr[] = ThrowableToStringArray.convert(ex);
            StringBuilder builder = new StringBuilder();
            for(String trace : traceStr) {
                builder.append(trace);
                builder.append("\n");
            }
            rpcTraceInfoVo.setResponseJson(builder.toString());
            throw ex;
        }finally {
            EagleEyeAgentFactory.getInstance().send2EagleEye(rpcTraceInfoVo);
            LogUtil.printSplunkLog(rpcTraceInfoVo);
        }
        log.debug("EagleEyeFeignClient around end...");
        return response;
    }

    //修改header头
    protected  Request modifiedRequest(Request request, TraceInfo traceInfo) {
        /*将鹰眼traceId放到feignClient中**/
        try {
            Field headersField = Request.class.getDeclaredField("headers");
            //将字段的访问权限设为true：即去除private修饰符的影响
            headersField.setAccessible(true);
            //去除final修饰符的影响，将字段设为可修改的
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(headersField, headersField.getModifiers() & ~Modifier.FINAL);
            Map<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
            List<String> traceId = new LinkedList<>();
            traceId.add(traceInfo.getTraceId());
            headers.put("hc-traceId", traceId);
            List<String> rpcId = new LinkedList<>();
            rpcId.add(traceInfo.getRpcId());
            headers.put("hc-rpcId", rpcId);
            headers.putAll(request.headers());
            headersField.set(request, Collections.unmodifiableMap(headers));
        } catch (Exception e) {
        }
        return request;
    }

    protected  void initRpcTraceInfoVo(RpcTraceInfoVo rpcTraceInfoVo,Request request, TraceInfo traceInfo) {
        rpcTraceInfoVo.setRequestDateTime(MixUtil.ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS.format(new Date()));
        rpcTraceInfoVo.setTraceId(traceInfo.getTraceId());
        rpcTraceInfoVo.setRpcId(traceInfo.getRpcId());
        rpcTraceInfoVo.setRpcType(RpcTypeEnum.FEIGN.getRpcName());
        URI asUri = URI.create(request.url());
        String serviceName = asUri.getHost();
        String methodName = StringUtils.removeStart(asUri.getPath(), "/");
        if (StringUtils.isBlank(methodName)) {
            methodName = asUri.getHost() + ":" + asUri.getPort();
        }
        rpcTraceInfoVo.setServiceCategory("feign");
        rpcTraceInfoVo.setServiceName(serviceName);
        rpcTraceInfoVo.setMethodName(methodName);
        rpcTraceInfoVo.setClientHost(HostUtil.getIp());
        rpcTraceInfoVo.setServerHost(request.url());
        //请求的参数
        boolean enableMvcParameter = EagleEyeAgentFactory.getInstance().getEagleEyeAgentConfig().isEnableMvcParameter();
        if (enableMvcParameter) {
            if (request.body() != null) {
                String bodyText = request.charset() != null ? new String(request.body(), request.charset()) : null;
                rpcTraceInfoVo.setRequestJson(bodyText != null ? bodyText : "Binary data");
                rpcTraceInfoVo.setRequestSize(request.body().length);
            } else {
                rpcTraceInfoVo.setRequestJson(request.method() + " " + request.url());
            }
        }
        rpcTraceInfoVo.setUserInfo(traceInfo.getUserInfo());
    }

    protected  Response fillResponseRpcTraceInfoVo(RpcTraceInfoVo rpcTraceInfoVo, Response response)  {
        if (response == null || rpcTraceInfoVo== null) {
            return response;
        }
        Response newResponse = response;
        int status = response.status();
        if (status >= 400) {
            rpcTraceInfoVo.setResult("ERROR");
        } else {
            rpcTraceInfoVo.setResult("OK");
        }
        //返回结果
        boolean enableMvcParameter = EagleEyeAgentFactory.getInstance().getEagleEyeAgentConfig().isEnableMvcParameter();
        if (enableMvcParameter) {
            Response.Body body = response.body();
            int bodyLength = 0;
            if (body != null && !(status == 204 || status == 205)) {
                // HTTP 204 No Content "...response MUST NOT include a message-body"
                // HTTP 205 Reset Content "...response MUST NOT include an entity"
                byte[] bodyData = new byte[0];
                try {
                    bodyData = Util.toByteArray(body.asInputStream());
                } catch (IOException e) {
                }
                bodyLength = bodyData.length;
                if (bodyLength > 0) {
                    rpcTraceInfoVo.setResponseJson(Util.decodeOrDefault(bodyData, UTF_8, "Binary data"));
                }
                //重新设置body，防止无法以流的方式二次读取
                newResponse = response.toBuilder().body(bodyData).build();
            } else {
                rpcTraceInfoVo.setResponseJson("");
            }
            rpcTraceInfoVo.setResponseSize(bodyLength);
        }
        return newResponse;
    }

}
